package com.example.easypan.service.impl;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.easypan.change.FileCategoryUtils;
import com.example.easypan.common.Result;
import com.example.easypan.pojo.FileInfo;
import com.example.easypan.pojo.PointFileIndexVo;
import com.example.easypan.service.FileInfoService;
import com.example.easypan.mapper.FileInfoMapper;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.*;

/**
* @author 小汤
* @description 针对表【file_info(文件信息)】的数据库操作Service实现
* @createDate 2025-02-05 16:53:55
*/
@Service
public class FileInfoServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo>
implements FileInfoService{

    private final Map<String, Integer> isMergePart = new HashMap<>();

    private final Map<String, List<String>> uploadProgress = new HashMap<>();

    @Resource
    private FileInfoMapper fileInfoMapper;

    @Override
    public List<FileInfo> selectBymd5(String fileHash) {
        LambdaQueryWrapper<FileInfo> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(FileInfo::getFileMd5,fileHash);
        return this.list(queryWrapper);
    }

    public List<FileInfo> selectPage(Integer pageNum, Integer pageSize, Integer pageFolderId) {
        return fileInfoMapper.selectPage(pageNum,pageSize,pageFolderId);
    }

    public Integer getCount() {
        return fileInfoMapper.getCount();
    }


    //分片上传逻辑
    public Result singleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid,int folderId,String fileMd5,Long fileSize) {
        //实际情况下，这些路径都应该是服务器上面存储文件的路径
        //当前文件目录下的路径
        String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径
        String tempPath = filePath + "temp\\" + fileUid;//临时文件存放路径

        File dir = new File(tempPath);
        if (!dir.exists()) dir.mkdirs();

        //生成一个临时文件名
        String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part";
        try {
            //将分片存储到临时文件夹中
            filePart.transferTo(new File(tempFileNamePath));

            //获得临时目录中所有文件
            File tempDir = new File(tempPath);
            File[] tempFiles = tempDir.listFiles();

            //one只是一个普通代码块，给break跳出使用的
            one:
            // 检查当前上传的分片数量是否与总分片数量partNum一致
            if (partNum.equals(Objects.requireNonNull(tempFiles).length)) {
                //需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成
                //判断是否有其他程序合并过，是的话就直接跳过
                if (isMergePart.get(fileUid) != null) {
                    break one;
                }
                isMergePart.put(fileUid, tempFiles.length);
                System.out.println("所有分片上传完成，预计总分片：" + partNum + "; 实际总分片:" + tempFiles.length);

                //最终的输出流
                FileOutputStream fileOutputStream = new FileOutputStream(filePath + fileName);

                //这里如果分片很多的情况下，可以采用多线程来执行
                for (int i = 0; i < partNum; i++) {
                    //读取分片数据，进行分片合并
                    //遍历获取分片
                    FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + fileName + "_" + i + ".part");
                    byte[] buf = new byte[1024 * 8];//8MB
                    int length;
                    while ((length = fileInputStream.read(buf)) != -1) {//读取fis文件输入字节流里面的数据
                        fileOutputStream.write(buf, 0, length);//通过fos文件输出字节流写出去
                    }
                    fileInputStream.close();
                }
                fileOutputStream.flush();
                fileOutputStream.close();

                //存到数据库里面
                String userId = "exampleUserId";  // 此处替换为实际的用户ID
                Date createTime = new Date();
                int dotIndex = fileName.lastIndexOf('.');
                String mainName = fileName.substring(0, dotIndex);
                String extName = fileName.substring(dotIndex + 1);
                String uploadedFilePath=filePath+fileName;
                String url = "http://localhost:9090/file/downloadBig/" + mainName + "." + extName;
                insertFileInfo(userId, fileMd5, mainName, extName, fileSize, uploadedFilePath, createTime,url,folderId);

                // 删除临时文件夹里面的分片文件 如果使用流操作且没有关闭输入流，可能导致删除失败
                for (int i = 0; i < partNum; i++) {
                    boolean delete = new File(tempPath + "\\" + fileName + "_" + i + ".part").delete();
                    File file = new File(tempPath + "\\" + fileName + "_" + i + ".part");
//                    System.out.println(i + " ; 是否删除：" + delete + " ; 是否还存在：" + file.exists());
                }
                //在删除对应的临时文件夹
                if (Objects.requireNonNull(tempDir.listFiles()).length == 0) {
                    boolean delete = tempDir.delete();
//                    System.out.println("文件夹 " + tempPath + "是否删除：" + delete);
                }
                isMergePart.remove(fileUid);



            }

        } catch (Exception e) {
            log.error("单文件分片上传失败!", e);
            return Result.error("单文件分片上传失败");
        }
        //通过返回成功的分片值，来验证分片是否有丢失
        return Result.success(partIndex.toString());
    }

    private void insertFileInfo(String userId, String fileHash, String mainName, String extName,
                                long fileSize, String filePath, Date createTime, String url, int folderId) {
        // 创建 FileInfo 实体对象
        FileInfo fileInfo = new FileInfo();
        fileInfo.setUserId(userId);
        fileInfo.setFileMd5(fileHash);
        fileInfo.setFileSize(fileSize);
        fileInfo.setFileName(mainName + "." + extName);
        fileInfo.setFileUrl(url);
        fileInfo.setFilePath(filePath);
        fileInfo.setCreateTime(createTime);
        fileInfo.setFolderType(0);  // 0: 文件
        fileInfo.setFolderId(folderId);
        int type= FileCategoryUtils.getFileCategory(extName);
        fileInfo.setFileCategory(type);  // 识别文件类型
        fileInfo.setDelFlag(2);  // 2: 正常
        this.save(fileInfo);
    }


    //检查文件上传
    public Result checkUploadFileIndex(PointFileIndexVo pointFileIndexVo) {
        try {
            //根据md5判断map里面是否存在
            List<String> list = uploadProgress.get(pointFileIndexVo.getFileMd5());
            //不存在创建一个队列
            if (list == null) list = new ArrayList<>();
            //有的话就把存放部分的队列存进去，不存在的话就存入一个空队列
            pointFileIndexVo.setParts(list);
            //存在就会有上传的部分，不存在当然就没有了
            System.out.println("已上传部分：" + list);
            return Result.success(pointFileIndexVo);
        } catch (Exception e) {
            log.error("上传文件检测异常!", e);
            return Result.error("上传文件检测异常！");
        }
    }

    //进行断点续传
    public Result singleFilePartPointUpload(MultipartFile filePart, String fileInfo) {
        // 将传入的文件信息（JSON 格式）解析成一个 PointFileIndexVo 对象
        PointFileIndexVo pointFileIndexVo = JSONObject.parseObject(fileInfo, PointFileIndexVo.class);

        //实际情况下，这些路径都应该是服务器上面存储文件的路径
        String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径
        // 临时文件存放路径，使用文件 MD5 作为文件夹名，以确保分片文件的唯一性
        String tempPath = filePath + "temp\\" + pointFileIndexVo.getFileMd5();//临时文件存放路径
        File dir = new File(tempPath);
        if (!dir.exists()) dir.mkdirs();

        //生成一个临时文件名
        String tempFileNamePath = tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + pointFileIndexVo.getPartIndex() + ".part";
        try {
            //将分片存储到临时文件夹中
            filePart.transferTo(new File(tempFileNamePath));

            // 获取当前文件 MD5 的分片索引列表，如果没有就初始化为空列表
            List<String> partIndex = uploadProgress.get(pointFileIndexVo.getFileMd5());
            if (Objects.isNull(partIndex)) {
                partIndex = new ArrayList<>();
            }

            // 将当前的分片索引加入到已上传分片的列表中
            partIndex.add(pointFileIndexVo.getPartIndex().toString());
            uploadProgress.put(pointFileIndexVo.getFileMd5(), partIndex);

            File tempDir = new File(tempPath);
            File[] tempFiles = tempDir.listFiles();

            // 检查是否所有的分片都上传完成
            one:
            if (pointFileIndexVo.getPartNum().equals(Objects.requireNonNull(tempFiles).length)) {
                //需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成
                if (isMergePart.get(pointFileIndexVo.getFileMd5()) != null) {
                    break one;
                }
                isMergePart.put(pointFileIndexVo.getFileMd5(), tempFiles.length);
                System.out.println("所有分片上传完成，预计总分片：" + pointFileIndexVo.getPartNum() + "; 实际总分片:" + tempFiles.length);
                //读取分片数据，进行分片合并
                FileOutputStream fileOutputStream = new FileOutputStream(filePath + pointFileIndexVo.getFileName());
                //这里如果分片很多的情况下，可以采用多线程来执行
                for (int i = 0; i < pointFileIndexVo.getPartNum(); i++) {
                    FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part");
                    byte[] buf = new byte[1024 * 8];//8MB
                    int length;
                    while ((length = fileInputStream.read(buf)) != -1) {//读取fis文件输入字节流里面的数据
                        fileOutputStream.write(buf, 0, length);//通过fos文件输出字节流写出去
                    }
                    fileInputStream.close();
                }
                fileOutputStream.flush();
                fileOutputStream.close();

                // 删除临时文件夹里面的分片文件 如果使用流操作且没有关闭输入流，可能导致删除失败
                for (int i = 0; i < pointFileIndexVo.getPartNum(); i++) {
                    boolean delete = new File(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part").delete();
                    File file = new File(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part");
//                    System.out.println(i + " ; 是否删除：" + delete + " ; 是否还存在：" + file.exists());
                }
                //在删除对应的临时文件夹
                if (Objects.requireNonNull(tempDir.listFiles()).length == 0) {
                    boolean delete = tempDir.delete();
//                    System.out.println("文件夹 " + tempPath + "是否删除：" + delete);
                }
                isMergePart.remove(pointFileIndexVo.getFileMd5());
                uploadProgress.remove(pointFileIndexVo.getFileMd5());
            }

        } catch (Exception e) {
            log.error("单文件分片上传失败!", e);
            return Result.error(pointFileIndexVo.getFileMd5(), "单文件分片上传失败");
        }
        //通过返回成功的分片值，来验证分片是否有丢失
        return Result.success(pointFileIndexVo.getFileMd5(), pointFileIndexVo.getPartIndex().toString());
    }
}
